/** @file r_camera.cpp
    @brief Camera */

#include "render/r_camera.h"

using namespace G3;

//==============================================================================
// Global settings
//==============================================================================
float G3::CAM_FPS_MAX_ROTX = 1.5f;

//==============================================================================
// Constructors
//==============================================================================
G3::CAMERA::CAMERA (float X, float Y, float Z) {
    Reset (VEC3F (X, Y, Z));
    Mode = GCAM_FPS;

    FocusDistance = 0.01f;
}

G3::CAMERA::CAMERA (VEC3F Pos) {
    Reset (Pos);
    Mode = GCAM_FPS;

    FocusDistance = 0.01f;
}

//==============================================================================
// Resets camera to a position
//==============================================================================
void G3::CAMERA::Reset (VEC3F Pos) {
    Rotation = QUATERNIONF ();
    Position = Pos;
	LastPosition = Position;

	model_matrix = GetViewMatrix ();
}

//==============================================================================
// Rotates the camera around the X axis
//==============================================================================
void G3::CAMERA::RotateX (float Angle) {
    QUATERNIONF q;
    float Angle2 = Angle;
    while (Angle2 > 360.0f) {
        Angle2 -= 360.0f;
    }
    while (Angle2 < -360.0f) {
        Angle2 += 360.0f;
    }
    q.CreateFromAxisAngle (Angle2, VEC3F (1, 0, 0));
    if (Mode == GCAM_6DOF) {
        Rotation = q * Rotation;
    } else {
        Rotation = Rotation * q;
    }
    Rotation.Normalize ();

	model_matrix = GetViewMatrix ();
}

//==============================================================================
// Rotates the camera around the Y axis
//==============================================================================
void G3::CAMERA::RotateY (float Angle) {
    QUATERNIONF q;
    float Angle2 = Angle;
    while (Angle2 > 360.0f) {
        Angle2 -= 360.0f;
    }
    while (Angle2 < -360.0f) {
        Angle2 += 360.0f;
    }
    q.CreateFromAxisAngle (Angle2, VEC3F (0, 1, 0));
    Rotation = q * Rotation;
    Rotation.Normalize ();

	model_matrix = GetViewMatrix ();
}

//==============================================================================
// Rotates the camera around the Y axis
//==============================================================================
void G3::CAMERA::RotateZ (float Angle) {
    QUATERNIONF q;
    float Angle2 = Angle;
    while (Angle2 > 360.0f) {
        Angle2 -= 360.0f;
    }
    while (Angle2 < -360.0f) {
        Angle2 += 360.0f;
    }
    q.CreateFromAxisAngle (Angle2, VEC3F (0, 0, 1));
    Rotation = q * Rotation;
    Rotation.Normalize ();

	model_matrix = GetViewMatrix ();
}

//==============================================================================
// Gets camera rotation matrix
//==============================================================================
MATRIX4x4F G3::CAMERA::GetRotationMatrix () {
    return Rotation.GetMatrix ();
}

//==============================================================================
// Gets camera view matrix
//==============================================================================
MATRIX4x4F G3::CAMERA::GetViewMatrix () {
    MATRIX4x4F M;

	M.Translate (-Position);

    return M * Rotation.GetMatrix ();
}

//==============================================================================
// Get the local Forward Vector by rotating the World Forward Vector
//==============================================================================
VEC3F G3::CAMERA::GetForwardVector () {
    return ApplyQuat (VEC3F (0, 0, 1), Rotation);
}

//==============================================================================
// Get the local Right Vector by rotating the World Right Vector
//==============================================================================
VEC3F G3::CAMERA::GetRightVector () {
    return ApplyQuat (VEC3F (1, 0, 0), Rotation);
}

//==============================================================================
// Get the local Up Vector by rotating the World Up Vector
//==============================================================================
VEC3F G3::CAMERA::GetUpVector () {
    return ApplyQuat (VEC3F (0, 1, 0), Rotation);
}

//==============================================================================
// Get the World Position of the camera
// Position variable is local (shift vector of the scene)
//==============================================================================
VEC3F G3::CAMERA::GetWorldPosition () {
    return Position;
}

//==============================================================================
// Gets the Near plane
// TODO:: Check
//==============================================================================
PLANE3F G3::CAMERA::GetNearPlane () {
    VEC3F n = GetForwardVector ();
    return PLANE3F (GetWorldPosition () + n * FocusDistance, n);
}

//==============================================================================
// Drags the camera by the Distance vector
//==============================================================================
void G3::CAMERA::Move (VEC3F Distance) {
    VEC3F r, u, f;

    LastPosition = Position;

    r = GetRightVector ();
    u = GetUpVector ();
	// Why does this have to be negative?
    f = -GetForwardVector ();

    if (Mode == GCAM_6DOF) {
        Position += r * Distance.x;
        Position += u * Distance.y;
        Position += f * Distance.z;
    } else {
        if (Mode == GCAM_FPS) {
            r.y = 0.0f;
            r.Normalize ();
            f.y = 0.0f;
            f.Normalize ();
        }

        Position += r * Distance.x;
        Position += VEC3F (0, 1, 0) * Distance.y;
        Position += f * Distance.z;
    }

	model_matrix = GetViewMatrix ();
}

//==============================================================================
// Rotates the camera
//==============================================================================
void G3::CAMERA::RotateView (float Angle, VEC3F Axis) {
    RotateX (Axis.x * Angle);
    RotateY (Axis.y * Angle);
    RotateZ (Axis.z * Angle);
}

//==============================================================================
// For rotating the camera with a mouse
//==============================================================================
void G3::CAMERA::DeltaRotate (float dX, float dY) {
    float dx = dX;
    float dy = dY;
    static VEC3F Rotated;

    Rotated.x += dY;
    Rotated.y += dX;

	// TODO:: Local 'Rotated' causes problems..
    if (Mode == GCAM_FPS || Mode == GCAM_SPECTATOR) {
        if (Rotated.x > CAM_FPS_MAX_ROTX) {
            Rotated.x = CAM_FPS_MAX_ROTX;
            dy = 0.0f;
        } else if (Rotated.x < -CAM_FPS_MAX_ROTX) {
            Rotated.x = -CAM_FPS_MAX_ROTX;
            dy = 0.0f;
        }
    }

    RotateView (G3_RAD2DEG (dy), VEC3F (-1, 0, 0));
    RotateView (G3_RAD2DEG (dx), VEC3F (0, 1, 0));
}

//==============================================================================
// Copies the camera structure
//==============================================================================
CAMERA G3::CAMERA::operator=(CAMERA Cam){
	Mode = Cam.Mode;
    Position = Cam.Position;
    LastPosition = Cam.LastPosition;
    Rotation = Cam.Rotation;
	FocusDistance = Cam.FocusDistance;

    return Cam;
}

//==============================================================================
// Sets camera mode
//==============================================================================
void G3::CAMERA::SetMode (uint NewMode) {
    if (NewMode > GCAM_MAX) {
        Mode = GCAM_MAX;
    }
    if (NewMode < GCAM_MIN) {
        Mode = GCAM_MIN;
    }

    Mode = NewMode;
}

//==============================================================================
// Normalizes camera rotation quaternion
//==============================================================================
void G3::CAMERA::Normalize () {
    Rotation.Normalize ();
}

//==============================================================================
// Focuses camera on a point
// TODO:: Should save Z-rotation - at the moment it is removed
//==============================================================================
void G3::CAMERA::FocusOn (VEC3F Point) {
	if (Point == Position)
		return;

	VEC3F pv = Point + Position;
	VEC2F v1;
	double a = 0.0, b = 0.0;
	QUATERNIONF q1, q2;

	// Top - XZ
	v1.x = pv.x;
	v1.y = pv.z;
	v1.Normalize ();

	a = G3_RAD2DEG (acosf (v1 * VEC2F (0.0f, 1.0f))) * G3_GETSIGN (CrossProduct (v1, VEC2F (0.0f, 1.0f)));

	// Right - ZY
	v1.x = (VEC2F (pv.x, pv.z)).GetLength ();
	v1.y = pv.y;
	v1.Normalize ();

	// Why do I have to do this?
	b = G3_RAD2DEG (acosf (v1 * VEC2F (0.0f, 1.0f))) - 90.0f;

	// Focus by rotating around X and Y axis
    q1.CreateFromAxisAngle (a, VEC3F (0, 1, 0));
	q2.CreateFromAxisAngle (b, VEC3F (1, 0, 0));
    Rotation = q1 * q2;
	Rotation.Normalize ();

	// Also set the focus distance
	FocusDistance = pv.GetLength ();

	model_matrix = GetViewMatrix ();
}

void G3::CAMERA::Render (float ElapsedTime) {

}

void G3::CAMERA::Update (float ElapsedTime) {
	int i = 0;

	// Update controllers
	for (i=0; i<(int)controllers.size (); i++) {
		if (controllers [i]) {
			controllers [i]->Update (ElapsedTime);
			// Apply transformation controllers
			if (controllers [i]->GetType () == CT_ROTATION)
				model_matrix = model_matrix * ((ROTATION_CTRL *) controllers [i])->matrix;
			else if (controllers [i]->GetType () == CT_TRANSLATION)
				model_matrix = model_matrix * ((TRANSLATION_CTRL *) controllers [i])->matrix;
			else if (controllers [i]->GetType () == CT_SCALE)
				model_matrix = model_matrix * ((SCALE_CTRL *) controllers [i])->matrix;
		}
	}

	// Update children
	for (i=0; i<(int)children.size (); i++) {
		if (children [i] != NULL)
			children [i]->Update (ElapsedTime);
	}
}
